page.tsx 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. 'use client';
  2. import './style.scss';
  3. import 'animate.css';
  4. import { use, useEffect, useState } from 'react';
  5. import { useDonationAlert } from '@/hooks/useDonationAlert';
  6. import { DonationAlertConfig } from '@/types/donation';
  7. import { fetchApi } from '@/lib/utils/client';
  8. import View from './view';
  9. type Props = {
  10. params: Promise<{ widgetToken: string }>;
  11. };
  12. const DEFAULT_CONFIG: DonationAlertConfig = {
  13. id: 0,
  14. title: '',
  15. amount: 0,
  16. matchType: 0,
  17. message: '{이름}님이 {금액}을 후원해주셨습니다!',
  18. playDelaySec: 0,
  19. displayDurationSec: 10,
  20. popupEffect: 'fadeIn',
  21. textEffect: null,
  22. nicknameFontFamily: null,
  23. nicknameFontSize: 24,
  24. nicknameFontColor: '#FFD700',
  25. amountFontFamily: null,
  26. amountFontSize: 24,
  27. amountFontColor: '#FF6B35',
  28. messageFontFamily: null,
  29. messageFontSize: 18,
  30. messageFontColor: '#FFFFFF',
  31. templateFontFamily: null,
  32. templateFontSize: 24,
  33. templateFontColor: '#FFFFFF',
  34. enableImage: true,
  35. imageUrl: '/resources/donate-default-image.gif',
  36. enableSound: true,
  37. soundUrl: '/sounds/default-donate-effect.mp3',
  38. isActive: true
  39. };
  40. function matchConfig(configs: DonationAlertConfig[], amount: number): DonationAlertConfig
  41. {
  42. // 1순위: Exact 매칭 (MatchType === 1)
  43. const exact = configs.find(c => c.matchType === 1 && c.amount === amount && c.isActive);
  44. if (exact) {
  45. return exact;
  46. }
  47. // 2순위: MinThreshold (MatchType === 0) — 금액 이상 중 가장 높은 것
  48. const thresholds = configs
  49. .filter(c => c.matchType === 0 && c.amount <= amount && c.isActive)
  50. .sort((a, b) => b.amount - a.amount);
  51. return thresholds[0] ?? DEFAULT_CONFIG;
  52. }
  53. export default function AlertPage({ params }: Props)
  54. {
  55. const { widgetToken } = use(params);
  56. const hubUrl = process.env.NEXT_PUBLIC_API_URL + '/hubs/donation';
  57. const [configs, setConfigs] = useState<DonationAlertConfig[]>([]);
  58. const { current, remoteState, onAlertComplete } = useDonationAlert(
  59. widgetToken,
  60. hubUrl,
  61. // 위젯이 실제 재생할 displayDurationSec 을 리모콘 타이머용으로 hub 에 동봉
  62. (alert) => matchConfig(configs, alert.amount).displayDurationSec
  63. );
  64. // API에서 config 목록 로드
  65. useEffect(() => {
  66. fetchApi<{ list: DonationAlertConfig[] }>(`/api/widget/alert/config/${widgetToken}`, { silent: true }).then(res => {
  67. if (res.success && res.data?.list) {
  68. setConfigs(res.data.list);
  69. }
  70. }).catch(() => {});
  71. }, [widgetToken]);
  72. const config = current ? matchConfig(configs, current.amount) : null;
  73. return (
  74. <div className="alert-page">
  75. {current && config && (
  76. <View
  77. key={current.alertID}
  78. alert={current}
  79. config={config}
  80. isAudioOnly={remoteState.isAudioOnly}
  81. isVideoOnly={remoteState.isVideoOnly}
  82. onComplete={onAlertComplete}
  83. />
  84. )}
  85. </div>
  86. );
  87. }